#ifndef XEN_EVENT_HPP
#define XEN_EVENT_HPP

#include "xen/pch.hpp"
#include "xen/core.hpp"
#include "xen/math.hpp"

namespace Xen {

enum class EventType {
    None = 0,
    WindowClose,
    WindowResize,

    KeyPressed,
    KeyReleased,
    KeyTyped,

    MouseMotion,
    MouseButtonPressed,
    MouseButtonReleased,
    MouseScrolled
};

enum EventCategory {
    None = BIT(0),
    EventCategoryApplication = BIT(1),
    EventCategoryInput = BIT(2),
    EventCategoryKeyboard = BIT(3),
    EventCategoryMouse = BIT(4),
    EventCategoryMouseButton = BIT(5)
};

#define EVENT_CLASS_TYPE(x) const char* GetName() const override { return #x; } \
                            static EventType GetStaticType() { return EventType::x; } \
                            EventType GetType() const override { return GetStaticType(); }

#define EVENT_CLASS_CATEGORY(x) int GetCategoryFlags() const override { return x; }

class Event {
 public:
    friend class EventDispatcher;

    Event() = default;
    virtual ~Event() = default;

    virtual EventType GetType() const = 0;
    virtual const char* GetName() const = 0;
    virtual int GetCategoryFlags() const = 0;
    virtual std::string ToString() const { return GetName(); }
    bool IsHandled() const { return is_handled_; }

    inline bool IsInCategory(EventCategory c) {
        return c & GetCategoryFlags();
    }

 private:
    bool is_handled_ = false;
};

class EventDispatcher {
 public:
    template <typename T>
    using EventFn = std::function<bool(T&)>;

    explicit EventDispatcher(Event& event): event_(event) {}

    template <typename T>
    bool Dispatch(EventFn<T> fn) {
        if (event_.GetType() == T::GetStaticType()) {
            event_.is_handled_ = fn(*(T*)(&event_));
            return true;
        }
        return false;
    }

 private:
    Event& event_;
};

#define BIND_EVENT_FN(x) std::bind(&x, this, std::placeholders::_1)

};

#endif
